# backend/modules/dataimport/routes.py

# 导入必要的库和模块
import os  # 处理文件和目录路径
import re  # 正则表达式操作
import uuid  # 生成唯一标识符
import unicodedata  # Unicode标准化
from flask_restful import Resource  # Flask-Restful的Resource基类
from flask import request, current_app  # Flask的请求和当前应用上下文
from flask_jwt_extended import jwt_required, get_jwt_identity  # JWT身份验证
import pandas as pd  # 数据处理库Pandas
from ...extensions import db  # 数据库扩展，db为SQLAlchemy的实例
from .models import UploadedFile  # 导入UploadedFile模型
from ..forecasting.models import Forecast  # 导入预测模型
from ..userinterface.models import InventoryManagement  # 导入库存管理模型
from ..authentication.models import User  # 导入用户模型

def custom_secure_filename(filename):
    """
    自定义的 secure_filename 函数，允许中文字符。
    将非法字符替换为下划线，同时保留中文字符、字母、数字、下划线、破折号和点。
    """
    # 先标准化 Unicode，将 filename 转换为规范形式
    filename = unicodedata.normalize('NFKC', filename)
    # 替换路径分隔符为下划线
    filename = filename.replace(os.path.sep, "_")
    # 使用正则表达式，保留中文字符、字母、数字、下划线、破折号和点，其他字符替换为下划线
    filename = re.sub(r'[^\w\-.一-龥]', '_', filename)
    return filename

class DataImportRoutes:
    def __init__(self, api):
        # 初始化数据导入路由，将各个 API 资源添加到指定的 URL 路径
        api.add_resource(DataImportAPI, '/api/data/import')  # 数据导入 API
        api.add_resource(UploadedFileListAPI, '/api/data/uploaded_files')  # 获取已上传文件列表 API
        api.add_resource(DataImportDeleteAPI, '/api/data/uploaded_files/delete/<int:file_id>')  # 删除上传文件 API

class DataImportAPI(Resource):
    @jwt_required()  # 需要 JWT 身份验证
    def post(self):
        # 获取当前用户的身份信息
        current_identity = get_jwt_identity()
        # 从数据库中获取当前用户
        user = User.query.filter_by(username=current_identity['username']).first()
        if not user:
            # 如果用户身份验证失败，返回401状态码和消息
            return {"message": "用户身份验证失败"}, 401

        # 检查请求中是否包含文件部分
        if 'file' not in request.files:
            return {"message": "没有文件部分"}, 400  # 返回400状态码

        # 获取上传的文件
        file = request.files['file']
        # 检查文件名是否为空
        if file.filename == '':
            return {"message": "没有选择文件"}, 400  # 返回400状态码

        # 检查文件是否存在且是允许的类型
        if file and allowed_file(file.filename):
            # 获取原始文件名并生成安全文件名
            original_filename = file.filename
            safe_filename = custom_secure_filename(original_filename)  # 使用自定义方法生成安全文件名
            # 使用 UUID 生成唯一文件名
            stored_filename = f"{uuid.uuid4().hex}_{safe_filename}"
            # 定义文件上传目录
            upload_dir = os.path.join(current_app.root_path, 'uploads')
            # 创建上传目录（如果目录不存在）
            os.makedirs(upload_dir, exist_ok=True)
            # 拼接出完整的文件路径
            filepath = os.path.join(upload_dir, stored_filename)
            try:
                # 保存文件到指定路径
                file.save(filepath)
                # 记录文件保存日志
                current_app.logger.info(f"文件已保存至 {filepath}")
            except Exception as e:
                # 记录文件保存失败的日志并返回500状态码
                current_app.logger.error(f"文件保存失败: {e}", exc_info=True)
                return {"message": "文件保存失败，请重试。"}, 500

            # 调用预处理函数
            try:
                preprocess_result = preprocess_data(filepath)  # 预处理数据
                # 检查预处理的结果是否包含失败信息
                if "失败" in preprocess_result:
                    return {"message": preprocess_result}, 500  # 返回500状态码

                preprocessed_path = preprocess_result  # 获取预处理后的文件路径

                # 保存上传文件记录到数据库
                uploaded_file = UploadedFile(
                    file_name=stored_filename,  # 文件名
                    preprocessed_path=preprocessed_path,  # 预处理后的文件路径
                    user_id=user.id  # 关联当前用户
                )
                # 将记录添加到数据库会话
                db.session.add(uploaded_file)
                # 提交会话
                db.session.commit()
                # 记录UploadedFile记录创建的日志
                current_app.logger.info(f"UploadedFile记录已创建，ID: {uploaded_file.id}")

                # 使用 Pandas 加载原始CSV文件
                df_original = pd.read_csv(filepath)
                # 记录加载原始CSV文件的日志
                current_app.logger.info("原始CSV文件已加载")

                # 按品类分组，提取最后一条记录
                df_inventory = df_original.sort_values('日期').groupby('品类').tail(1)[['品类', '当前库存数量']]
                # 记录提取的库存数据
                current_app.logger.info(f"提取到的库存数据:\n{df_inventory}")

                # 遍历提取的库存数据
                for _, row in df_inventory.iterrows():
                    product_name = row['品类']  # 获取产品名称
                    current_stock = row['当前库存数量']  # 获取当前库存数量

                    # 查询现有库存管理记录
                    inventory = InventoryManagement.query.filter_by(
                        product_name=product_name,
                        uploaded_file_id=uploaded_file.id  # 根据文件 ID 查询
                    ).first()

                    if inventory:  # 如果记录存在
                        # 更新库存数量
                        inventory.current_stock = current_stock
                        # 记录更新InventoryManagement记录的日志
                        current_app.logger.info(f"更新InventoryManagement记录: 品类={product_name}, 库存={current_stock}")
                    else:  # 如果记录不存在
                        # 创建新的库存管理记录
                        inventory = InventoryManagement(
                            product_name=product_name,  # 产品名称
                            current_stock=current_stock,  # 当前库存数量
                            uploaded_file_id=uploaded_file.id  # 关联上传文件 ID
                        )
                        # 添加库存管理记录到数据库会话
                        db.session.add(inventory)
                        # 记录创建InventoryManagement记录的日志
                        current_app.logger.info(f"创建InventoryManagement记录: 品类={product_name}, 库存={current_stock}")

                # 提交库存管理记录
                db.session.commit()
                # 记录所有InventoryManagement记录已提交的日志
                current_app.logger.info("所有InventoryManagement记录已提交")

                # 返回成功消息和文件 ID
                return {"message": "文件上传并预处理成功", "file_id": uploaded_file.id}, 200
            except Exception as e:
                # 回滚数据库会话
                db.session.rollback()
                # 记录文件预处理失败的日志
                current_app.logger.error(f"文件预处理失败: {e}", exc_info=True)
                return {"message": f"文件预处理失败: {str(e)}"}, 500  # 返回500状态码
        else:
            return {"message": "不支持的文件类型"}, 400  # 返回400状态码

def allowed_file(filename):
    # 检查文件名是否包含扩展名，并且扩展名为csv类型
    return '.' in filename and filename.rsplit('.', 1)[1].lower() == 'csv'

class UploadedFileListAPI(Resource):
    @jwt_required()  # 需要 JWT 身份验证
    def get(self):
        # 获取当前用户的身份信息
        current_identity = get_jwt_identity()
        # 从数据库中获取当前用户
        current_user = User.query.filter_by(username=current_identity['username']).first()
        if not current_user:
            return {"message": "用户身份验证失败"}, 401  # 如果用户不存在，返回401状态码

        try:
            # 根据用户角色查询上传的文件列表
            if current_user.role == '管理员':
                # 管理员获取所有文件
                uploaded_files = UploadedFile.query.order_by(UploadedFile.created_at.desc()).all()
            else:
                # 普通用户获取自己的文件
                uploaded_files = UploadedFile.query.filter_by(user_id=current_user.id).order_by(UploadedFile.created_at.desc()).all()

            # 构建文件列表
            file_list = [
                {
                    "id": file.id,  # 文件 ID
                    "file_name": file.file_name,  # 文件名称
                    "preprocessed_path": file.preprocessed_path,  # 预处理文件路径
                    "created_at": file.created_at.strftime('%Y-%m-%d %H:%M:%S'),  # 创建时间格式化
                    "username": file.user.username  # 添加上传者用户名
                }
                for file in uploaded_files
            ]
            # 返回文件列表
            return {"uploaded_files": file_list}, 200
        except Exception as e:
            # 记录获取上传文件列表失败的日志
            current_app.logger.error(f"获取上传文件列表失败: {e}")
            return {"message": "获取上传文件列表失败，请重试。"}, 500  # 返回500状态码

class DataImportDeleteAPI(Resource):
    @jwt_required()  # 需要 JWT 身份验证
    def delete(self, file_id):
        # 获取当前用户的身份信息
        current_identity = get_jwt_identity()
        # 从数据库中获取当前用户
        current_user = User.query.filter_by(username=current_identity['username']).first()
        if not current_user:
            return {"message": "用户身份验证失败"}, 401  # 如果用户不存在，返回401状态码

        try:
            # 查询要删除的上传文件
            uploaded_file = UploadedFile.query.get(file_id)
            if not uploaded_file:
                return {"message": "上传文件不存在"}, 404  # 如果文件不存在，返回404状态码

            # 权限控制
            if current_user.role == '管理员':
                # 获取上传该文件的用户
                target_user = uploaded_file.user
                # 如果是其他管理员且当前用户不是超级用户
                if target_user.role == '管理员' and not current_user.is_pzy():
                    return {"message": "您无权删除其他管理员的数据"}, 403  # 返回403状态码
            else:
                # 普通用户只能删除自己的文件
                if uploaded_file.user_id != current_user.id:  # 检查文件的上传用户 ID
                    return {"message": "未授权删除其他用户的数据"}, 403  # 返回403状态码

            # 删除预处理文件
            if os.path.exists(uploaded_file.preprocessed_path):  # 检查预处理文件是否存在
                os.remove(uploaded_file.preprocessed_path)  # 删除预处理文件
            else:
                # 记录预处理文件不存在的警告日志
                current_app.logger.warning(f"预处理文件不存在: {uploaded_file.preprocessed_path}")

            # 删除原始上传文件
            upload_dir = os.path.join(current_app.root_path, 'uploads')  # 获取上传目录
            stored_filepath = os.path.join(upload_dir, uploaded_file.file_name)  # 拼接原始上传文件路径
            if os.path.exists(stored_filepath):  # 检查原始文件是否存在
                os.remove(stored_filepath)  # 删除原始上传文件
                # 记录删除上传文件的日志
                current_app.logger.info(f"删除上传文件: {stored_filepath}")
            else:
                # 记录上传文件不存在的警告日志
                current_app.logger.warning(f"上传文件不存在: {stored_filepath}")

            # 删除关联的预测记录
            forecasts = Forecast.query.filter_by(uploaded_file_id=file_id).all()  # 查找与文件相关的预测记录
            for forecast in forecasts:
                db.session.delete(forecast)  # 删除预测记录

            # 删除上传文件记录
            db.session.delete(uploaded_file)  # 删除上传文件记录
            # 提交数据库会话
            db.session.commit()
            return {"message": "上传文件及关联数据删除成功"}, 200  # 返回成功消息
        except Exception as e:
            # 回滚数据库会话
            db.session.rollback()
            # 记录删除上传文件失败的日志
            current_app.logger.error(f"删除上传文件失败: {e}", exc_info=True)
            return {"message": "删除上传文件失败，请重试。"}, 500  # 返回500状态码

def preprocess_data(file_path):
    """
    数据预处理函数。

    参数:
        file_path (str): 上传的原始文件路径。

    返回:
        str: 预处理后的文件路径或错误消息。
    """
    try:
        # 获取文件扩展名
        _, file_extension = os.path.splitext(file_path)  # 使用 os.path.splitext 分割文件路径，获取文件名和扩展名
        file_extension = file_extension.lower()  # 将文件扩展名转换为小写以确保一致性

        # 根据文件类型读取数据
        if file_extension == '.csv':
            df = pd.read_csv(file_path)  # 读取 CSV 文件并将其存储到 DataFrame 中
        else:
            return "不支持的文件类型"  # 如果文件类型不被支持，则返回相应的错误信息

        # 数据清洗：删除缺失值
        df.dropna(inplace=True)  # 删除 DataFrame 中所有含有缺失值的行

        # 确保必要的列存在
        required_columns = ['日期', '品类', '当前订单数量', '当前库存数量', '历史补货数量']  # 定义预处理所需的必要列名
        for col in required_columns:
            if col not in df.columns:
                return f"数据中缺少 '{col}' 列"  # 如果 DataFrame 中缺少必要的列，则返回错误信息

        # 转换 '日期' 列为 datetime 类型
        df['日期'] = pd.to_datetime(df['日期'], errors='coerce')  # 将 '日期' 列转换为 datetime 类型，无法解析的日期将被设置为 NaT
        if df['日期'].isnull().any():
            return "日期列存在无效日期格式"  # 如果日期列中存在无效的日期格式，则返回错误信息

        # 提取日期特征
        df['年'] = df['日期'].dt.year  # 从 '日期' 列中提取年份，并存储到新列 '年' 中
        df['月'] = df['日期'].dt.month  # 从 '日期' 列中提取月份，并存储到新列 '月' 中
        df['日'] = df['日期'].dt.day  # 从 '日期' 列中提取日，并存储到新列 '日' 中
        df['星期'] = df['日期'].dt.weekday  # 从 '日期' 列中提取星期几（0=周一，6=周日），并存储到新列 '星期' 中

        # 处理非数值列（排除 '日期' 和 '品类'）
        non_numeric_columns = [col for col in df.select_dtypes(include=['object', 'category']).columns if
                               col not in ['日期', '品类']]  # 选择所有非数值类型的列，排除 '日期' 和 '品类' 列
        if non_numeric_columns:
            # 将非数值列转换为虚拟变量（one-hot 编码），并排除每个类别的第一个值以避免虚拟变量陷阱
            df = pd.get_dummies(df, columns=non_numeric_columns, drop_first=True)

        # 保存预处理后的数据
        preprocessed_dir = os.path.join(os.path.dirname(file_path), 'preprocessed_data')  # 定义预处理后数据的保存目录
        os.makedirs(preprocessed_dir, exist_ok=True)  # 如果保存目录不存在，则创建该目录
        processed_filename = os.path.splitext(os.path.basename(file_path))[0] + '_processed.csv'  # 生成处理后文件的名称
        processed_file_path = os.path.join(preprocessed_dir, processed_filename)  # 获取完整的保存路径
        df.to_csv(processed_file_path, index=False, encoding='utf-8')  # 将处理后的 DataFrame 保存为 CSV 文件，且不保留索引

        return processed_file_path  # 返回预处理后的文件路径

    except Exception as e:
        return f"数据预处理失败: {str(e)}"  # 捕获所有异常并返回相应的错误信息